package com.linciping.useges.plugin.util;

import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.linciping.useges.plugin.bean.ModuleInfo;
import com.linciping.useges.plugin.bean.PsiMergeDirectory;
import com.linciping.useges.plugin.bean.UsegesNode;
import com.linciping.useges.plugin.listener.SourceFileFilter;
import com.linciping.useges.plugin.search.UsegesFilter;
import com.twelvemonkeys.util.FilterIterator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.java.JavaResourceRootType;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import org.jetbrains.kotlin.config.ResourceKotlinRootType;
import org.jetbrains.kotlin.config.SourceKotlinRootType;
import org.jetbrains.kotlin.psi.KtFile;

import java.util.*;

public class ProjectFileUtil {

    /**
     * 获取project项目文件
     * 因为使用Project.getProjectFile()返回的是${projectPath}/.idea/misc.xml文件
     *
     * @param project
     * @return
     */
    @Nullable
    public static PsiDirectory getProjectDirectory(Project project) {
        if (project == null) return null;
        String projectName = project.getName();
        VirtualFile[] projectRoots = ProjectRootManager.getInstance(project).getContentRoots();
        VirtualFile projectFile = findFileByName(projectRoots, projectName);
        if (projectFile == null) return null;
        return PsiManager.getInstance(project).findDirectory(projectFile);
    }

    /**
     * 获取module项目文件
     *
     * @param module
     * @return
     */
    @Nullable
    public static PsiDirectory getModuleDirectory(Module module) {
        if (module == null) return null;
        String moduleName = getModuleName(module);
        VirtualFile[] moduleRoots = ModuleRootManager.getInstance(module).getContentRoots();
        VirtualFile moduleRoot = findFileByName(moduleRoots, moduleName);
        if (moduleRoot == null) return null;
        return PsiManager.getInstance(module.getProject()).findDirectory(moduleRoot);
    }

    public static VirtualFile findFileByName(VirtualFile[] virtualFiles, String name) {
        for (VirtualFile virtualFile : virtualFiles) {
            String path = virtualFile.getPath();
            if (Util.isPathEnd(path, name)) {
                return virtualFile;
            }
        }
        return null;
    }

    public static String getModuleName(Module module) {
        String name = module.getName();
        String projectName = module.getProject().getName();
        return name.replace(projectName, "").replace(".", "");
    }

    /**
     * 获取项目下所有的模块
     *
     * @param project
     * @return
     */
    public static List<Module> getModuleList(Project project) {
        ModuleManager moduleManager = ModuleManager.getInstance(project);
        Module[] modules = moduleManager.getModules();
        return new ArrayList<>(Arrays.asList(modules));
    }

    /**
     * 获取项目下所有的模块，支持过滤操作
     *
     * @param project
     * @param filter
     * @return
     */
    public static List<Module> getModuleList(Project project, FilterIterator.Filter<Module> filter) {
        ModuleManager moduleManager = ModuleManager.getInstance(project);
        Module[] modules = moduleManager.getModules();
        List<Module> moduleList = new ArrayList<>();
        for (Module module : modules) {
            if (filter.accept(module)) {
                moduleList.add(module);
            }
        }
        return moduleList;
    }

    public static List<ModuleInfo> getProjectSourceList(Project project, FilterIterator.Filter<Module> filter, boolean isOnlyJava, boolean isFilterBuildSource) {
        Set<JpsModuleSourceRootType<?>> sourceRootTypes = getSourceTypeSet(isOnlyJava);
        ModuleManager moduleManager = ModuleManager.getInstance(project);
        Module[] modules = moduleManager.getModules();
        List<ModuleInfo> moduleInfoList = new ArrayList<>();
        for (Module module : modules) {
            if (filter.accept(module)) {
                PsiDirectory moduleDirectory = getModuleDirectory(module);
                List<VirtualFile> sourceFileList = getModuleSourceList(module, sourceRootTypes, isFilterBuildSource);
                if (!CollectionUtil.isListEmpty(sourceFileList)) {
                    moduleInfoList.add(new ModuleInfo(moduleDirectory, sourceFileList));
                }
            }
        }
        return moduleInfoList;
    }

    public static List<VirtualFile> getModuleSourceList(Module module, boolean isOnlyJava, boolean isFilterBuildSource) {
        Set<JpsModuleSourceRootType<?>> sourceRootTypes = getSourceTypeSet(isOnlyJava);
        return getModuleSourceList(module, sourceRootTypes, isFilterBuildSource);
    }

    public static List<VirtualFile> getModuleResourceList(Module module, boolean isOnlyJava, boolean isFilterBuildSource) {
        Set<JpsModuleSourceRootType<?>> sourceRootTypes = getResourceTypeSet(isOnlyJava);
        return getModuleSourceList(module, sourceRootTypes, isFilterBuildSource);
    }

    public static List<VirtualFile> getModuleSourceList(Module module, Set<JpsModuleSourceRootType<?>> sourceRootTypes, boolean isFilterBuildSource) {
        List<VirtualFile> sourceFileList = ModuleRootManager.getInstance(module).getSourceRoots(sourceRootTypes);
        if (isFilterBuildSource) {
            String moduleName = ProjectFileUtil.getModuleName(module);
            List<VirtualFile> resultSourceFileList = new ArrayList<>();
            for (VirtualFile sourceFile : sourceFileList) {
                if (!isBuildSource(moduleName, sourceFile.getPath())) {
                    resultSourceFileList.add(sourceFile);
                }
            }
            return resultSourceFileList;
        } else {
            return ModuleRootManager.getInstance(module).getSourceRoots(sourceRootTypes);
        }
    }

    public static String getJavaFileFullName(PsiJavaFile psiJavaFile) {
        return psiJavaFile.getPackageName() + "." + getJavaFileName(psiJavaFile);
    }

    public static String getJavaFileName(PsiJavaFile psiJavaFile) {
        PsiClass[] psiClasses = psiJavaFile.getClasses();
        String className;
        if (psiClasses.length != 0) {
            PsiClass psiClass = psiJavaFile.getClasses()[0];
            className = psiClass.getName();
        } else {
            String filename = psiJavaFile.getName();
            filename = filename.split(".java")[0];
            className = filename;
        }
        return className;
    }

    public static String getKtFullName(KtFile ktFile) {
        String packageName = ktFile.getPackageName();
        String ktFileName = getKtName(ktFile);
        return packageName + "." + ktFileName;
    }

    public static String getKtName(KtFile ktFile) {
        return ktFile.getName().replace(".kt", "");
    }

    public static boolean isBuildSource(String moduleName, String path) {
        String[] paths = path.split("/");
        if (paths.length == 1) {
            return path.contains("build");
        } else {
            for (int i = 0; i < paths.length; i++) {
                String pathItem = paths[i];
                if (pathItem.equals(moduleName)) {
                    int index = i + 1;
                    String next = paths[index];
                    return next.equals("build");
                }
            }
            return path.contains("build");
        }
    }

    public static boolean moduleIsProject(Module module) {
        Project project = module.getProject();
        return module.getName().equals(project.getName());
    }

    public static Set<JpsModuleSourceRootType<?>> getSourceTypeSet(boolean isOnlyJava) {
        Set<JpsModuleSourceRootType<?>> sourceRootTypes;
        if (!isOnlyJava) {
            sourceRootTypes = new HashSet<>(2);
            sourceRootTypes.add(JavaSourceRootType.SOURCE);
            sourceRootTypes.add(SourceKotlinRootType.INSTANCE);
        } else {
            sourceRootTypes = new HashSet<>(2);
            sourceRootTypes.add(JavaSourceRootType.SOURCE);
        }
        return sourceRootTypes;
    }

    public static Set<JpsModuleSourceRootType<?>> getResourceTypeSet(boolean isOnlyJava) {
        Set<JpsModuleSourceRootType<?>> sourceRootTypes;
        if (!isOnlyJava) {
            sourceRootTypes = new HashSet<>(2);
            sourceRootTypes.add(JavaResourceRootType.RESOURCE);
            sourceRootTypes.add(ResourceKotlinRootType.INSTANCE);
        } else {
            sourceRootTypes = new HashSet<>(2);
            sourceRootTypes.add(JavaSourceRootType.SOURCE);
        }
        return sourceRootTypes;
    }

    public static boolean hasDependencies(Module[] dependencies, Module targetModule) {
        if (dependencies.length == 0) return false;
        for (Module module : dependencies) {
            if (module.equals(targetModule)) {
                return true;
            }
        }
        return false;
    }

    public static UsegesNode createSourceNode(Project project, Module module, List<VirtualFile> sourceFileList, List<UsegesNode> sourceFileNodeList, boolean canNavigate) {
        if (CollectionUtil.isListEmpty(sourceFileList)) return null;
        UsegesNode root = new UsegesNode(null, getModuleDirectory(module), new ArrayList<>(), canNavigate);
        PsiManager psiManager = PsiManager.getInstance(project);
        for (VirtualFile virtualFile : sourceFileList) {
            PsiDirectory psiDirectory = psiManager.findDirectory(virtualFile);
            UsegesNode child = createSourceNodeByPsiDirectory(root, psiDirectory, sourceFileNodeList, canNavigate);
            if (child != null) {
                root.getChildrenList().add(child);
            }
        }
        return root;
    }

    private static UsegesNode createSourceNodeByPsiDirectory(UsegesNode parent, PsiDirectory psiDirectory, List<UsegesNode> sourceFileNodeList, boolean canNavigate) {
        if (psiDirectory == null) {
            return null;
        }
        if (isPackage(psiDirectory) && psiDirectory.getChildren().length == 1) {
            PsiElement psiElement = psiDirectory.getChildren()[0];
            if (psiElement instanceof PsiDirectory) {
                PsiMergeDirectory psiMergeDirectory = new PsiMergeDirectory(psiDirectory.getName(), (PsiDirectory) psiElement);
                UsegesNode childNode = createSourceNodeByPsiDirectory(parent, psiMergeDirectory, sourceFileNodeList, canNavigate);
                if (childNode != null) {
                    parent.getChildrenList().add(childNode);
                }
            } else if (psiElement instanceof PsiJavaFile) {
                PsiJavaFile psiJavaFile = (PsiJavaFile) psiElement;
                UsegesNode usegesNode = new UsegesNode(parent, psiJavaFile, new ArrayList<>(), canNavigate);
                sourceFileNodeList.add(usegesNode);
                parent.getChildrenList().add(usegesNode);
            } else if (psiElement instanceof KtFile) {
                KtFile ktFile = (KtFile) psiElement;
                UsegesNode usegesNode = new UsegesNode(parent, ktFile, new ArrayList<>(), canNavigate);
                sourceFileNodeList.add(usegesNode);
                parent.getChildrenList().add(usegesNode);
            }
            return null;
        }
        UsegesNode node = new UsegesNode(parent, psiDirectory, new ArrayList<>(), canNavigate);
        PsiFile[] psiFiles = psiDirectory.getFiles();
        if (psiFiles.length > 0) {
            for (PsiFile psiFile : psiFiles) {
                if ((psiFile instanceof PsiJavaFile)) {
                    PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile;
                    UsegesNode usegesNode = new UsegesNode(node, psiJavaFile, new ArrayList<>(), canNavigate);
                    sourceFileNodeList.add(usegesNode);
                    node.getChildrenList().add(usegesNode);
                } else if (psiFile instanceof KtFile) {
                    KtFile ktFile = (KtFile) psiFile;
                    UsegesNode usegesNode = new UsegesNode(node, ktFile, new ArrayList<>(), canNavigate);
                    sourceFileNodeList.add(usegesNode);
                    node.getChildrenList().add(usegesNode);
                }
            }
        }
        PsiDirectory[] psiDirectories = psiDirectory.getSubdirectories();
        if (psiDirectories.length > 0) {
            for (PsiDirectory child : psiDirectories) {
                UsegesNode childNode = createSourceNodeByPsiDirectory(node, child, sourceFileNodeList, canNavigate);
                if (childNode != null) {
                    node.getChildrenList().add(childNode);
                }
            }
        }
        return node;
    }

    public static void createUsegesNodeByModule(Project project, ModuleInfo moduleInfo, UsegesNode parent, UsegesFilter usegesFilter) {
        PsiNamedElement module = moduleInfo.getModule();
        UsegesNode moduleNode = new UsegesNode(parent, module, new ArrayList<>(), false);
        for (VirtualFile sourceDir : moduleInfo.getSourceFileList()) {
            PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(sourceDir);
            UsegesNode sourceDirNode = createUsegesNodeBySourceDir(psiDirectory, moduleNode, usegesFilter);
            if (sourceDirNode != null && !CollectionUtil.isListEmpty(sourceDirNode.getChildrenList())) {
                moduleNode.getChildrenList().add(sourceDirNode);
            }
        }
        if (!CollectionUtil.isListEmpty(moduleNode.getChildrenList())) {
            parent.getChildrenList().add(moduleNode);
        }
    }

    public static UsegesNode createUsegesNodeBySourceDir(@Nullable PsiDirectory sourceDir, UsegesNode parent, @NotNull SourceFileFilter sourceFileFilter) {
        if (sourceDir == null) {
            return null;
        }
        if (isPackage(sourceDir) && sourceDir.getChildren().length == 1) {
            PsiElement psiElement = sourceDir.getChildren()[0];
            if (psiElement instanceof PsiDirectory) {
                PsiMergeDirectory psiMergeDirectory = new PsiMergeDirectory(sourceDir.getName(), (PsiDirectory) psiElement);
                UsegesNode childNode = createUsegesNodeBySourceDir(psiMergeDirectory, parent, sourceFileFilter);
                if (childNode != null && !CollectionUtil.isListEmpty(childNode.getChildrenList())) {
                    parent.getChildrenList().add(childNode);
                }
            } else if (psiElement instanceof PsiJavaFile) {
                PsiJavaFile psiJavaFile = (PsiJavaFile) psiElement;
                int fileHashCode = psiJavaFile.hashCode();
                List<String> importPackageList = AsyncUtil.getPsiJavaFileImport(psiJavaFile);
                if (sourceFileFilter.isFilterFile(fileHashCode, importPackageList)) {
                    UsegesNode sourceNode = new UsegesNode(parent, psiJavaFile, null, true);
                    parent.getChildrenList().add(sourceNode);
                }
            } else if (psiElement instanceof KtFile) {
                KtFile ktFile = (KtFile) psiElement;
                int fileHashCode = ktFile.hashCode();
                List<String> importPackageList = AsyncUtil.getPsiKtFileImport(ktFile);
                if (sourceFileFilter.isFilterFile(fileHashCode, importPackageList)) {
                    UsegesNode sourceNode = new UsegesNode(parent, ktFile, null, true);
                    parent.getChildrenList().add(sourceNode);
                }
            }
            return null;
        }
        UsegesNode node = new UsegesNode(parent, sourceDir, new ArrayList<>(), false);
        PsiFile[] psiFiles = sourceDir.getFiles();
        if (psiFiles.length > 0) {
            for (PsiFile psiFile : psiFiles) {
                if ((psiFile instanceof PsiJavaFile)) {
                    PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile;
                    int fileHashCode = psiJavaFile.hashCode();
                    List<String> importPackageList = AsyncUtil.getPsiJavaFileImport(psiJavaFile);
                    if (sourceFileFilter.isFilterFile(fileHashCode, importPackageList)) {
                        UsegesNode sourceNode = new UsegesNode(parent, psiJavaFile, null, true);
                        parent.getChildrenList().add(sourceNode);
                    }
                } else if (psiFile instanceof KtFile) {
                    KtFile ktFile = (KtFile) psiFile;
                    int fileHashCode = ktFile.hashCode();
                    List<String> importPackageList = AsyncUtil.getPsiKtFileImport(ktFile);
                    if (sourceFileFilter.isFilterFile(fileHashCode, importPackageList)) {
                        UsegesNode sourceNode = new UsegesNode(parent, ktFile, null, true);
                        parent.getChildrenList().add(sourceNode);
                    }
                }
            }
        }
        PsiDirectory[] psiDirectories = sourceDir.getSubdirectories();
        if (psiDirectories.length > 0) {
            for (PsiDirectory child : psiDirectories) {
                UsegesNode childNode = createUsegesNodeBySourceDir(child, node, sourceFileFilter);
                if (childNode != null && !CollectionUtil.isListEmpty(childNode.getChildrenList())) {
                    node.getChildrenList().add(childNode);
                }
            }
        }
        return node;
    }

    public static String getRootPackageName(UsegesNode rootNode) {
        if (isPackage(rootNode.getPsiNamedElement())) {
            return getPackageName(rootNode);
        } else {
            List<String> rootPackageNameList = new ArrayList<>();
            for (UsegesNode child : rootNode.getChildrenList()) {
                String packageName = getRootPackageName(child);
                rootPackageNameList.add(packageName);
            }
            if (rootPackageNameList.size() <= 1) return rootPackageNameList.get(0);
            return Collections.min(rootPackageNameList, (o1, o2) -> {
                int level1 = o1.split("\\.").length;
                int level2 = o2.split("\\.").length;
                return Integer.compare(level1, level2);
            });
        }
    }

    public static String getPackageName(UsegesNode usegesNode) {
        StringBuilder packageName = new StringBuilder();
        if (isPackage(usegesNode.getPsiNamedElement())) {
            packageName.append(usegesNode.getPsiNamedElement().getName());
            if (usegesNode.getChildrenList().size() == 1) {
                packageName.append(".").append(getPackageName(usegesNode.getChildrenList().get(0)));
            } else {
                return packageName.toString();
            }
        }
        return packageName.toString();
    }

    public static boolean isPackage(PsiNamedElement psiNamedElement) {
        if (psiNamedElement instanceof PsiDirectory) {
            return isPackage((PsiDirectory) psiNamedElement);
        }
        return false;
    }

    public static boolean isPackage(@NotNull PsiDirectory directory) {
        ProjectFileIndex fileIndex = ProjectRootManager.getInstance(directory.getProject()).getFileIndex();
        VirtualFile virtualFile = directory.getVirtualFile();
        String packageName = fileIndex.getPackageNameByDirectory(virtualFile);
        boolean isPackage = packageName != null && packageName.length() > 0;
        return fileIndex.isUnderSourceRootOfType(virtualFile, JavaModuleSourceRootTypes.SOURCES) && isPackage;
    }
}
